package com.ccb.sc.login.controller;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
import com.ccb.sc.auth.TokenAuth;
import com.ccb.sc.auth.TokenModel;
import com.ccb.sc.auth.TokenUtil;
import com.ccb.sc.constant.AppConst;
import com.ccb.sc.login.vo.WxLoginVo;
import com.ccb.sc.member.service.AppMemberService;
import com.ccb.sc.common.modules.member.service.UserThirdAuthService;
import com.ccb.sc.member.vo.AppMemberVo;
import com.ccb.sc.common.bind.annotation.CurrentUser;
import com.ccb.sc.common.bind.annotation.Limit;
//psl import com.ccb.sc.common.bind.exception.BizException;
import com.ccb.sc.common.bind.response.ResBody;
import com.ccb.sc.common.code.BizCodeFace;
import com.ccb.sc.common.code.ErrorCode;
import com.ccb.sc.common.enums.Gender;
import com.ccb.sc.common.modules.member.entity.Member;
import com.ccb.sc.common.modules.member.entity.UserThirdAuth;
import com.ccb.sc.common.modules.member.enums.LoginType;
import com.ccb.sc.common.modules.member.enums.MemberSource;
import com.ccb.sc.common.modules.member.enums.MemberStatus;
import com.ccb.sc.common.modules.member.service.MaService;
import com.ccb.sc.common.util.oss.OssManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Objects;

/**
 * @author ken
 * @version 1.0
 * @date 2022-08-05 1:19
 */
@Slf4j
@RestController
@RequestMapping("/app/wx")
@Api(value = "微信登录", tags = "微信登录")
public class WxMaUserController {

    @Autowired
    private MaService maService;

    @Autowired
    private AppMemberService memberService;

    @Autowired
    private UserThirdAuthService userThirdAuthService;

    @Autowired
    private OssManager ossManager;

    @Value("${aliyun.oss.bucket:'mifen-vip'}")
    private String bucketsName;

    @Value("${spring.profiles.active:'prd'}")
    private String env;

    @PostMapping("/login")
    @ApiOperation(value = "微信登录", response = AppMemberVo.class)
    public ResBody<?> login(String code) {
        if (!StringUtils.hasText(code)) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("微信登录失败，code is null"));
        }
        try {
            WxMaJscode2SessionResult sessionResult = maService.getWxMaService().getUserService().getSessionInfo(code);
            WxLoginVo wxLoginVo = new WxLoginVo();
            wxLoginVo.setSessionKey(sessionResult.getSessionKey());
            wxLoginVo.setOpenId(sessionResult.getOpenid());
            wxLoginVo.setUnionId(sessionResult.getUnionid());
            log.info("wxLoginVo:{}", wxLoginVo);
            return ResBody.success(wxLoginVo);
        } catch (WxErrorException e) {
            log.error(e.getMessage(), e);
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("微信登录失败，未知错误"));
        } finally {
            //清理ThreadLocal
            WxMaConfigHolder.remove();
        }
        return ResBody.failure();
    }

    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/info")
    @ApiOperation(value = "获取微信用户信息")
    public ResBody<?> info(@RequestParam String openId, @RequestParam String unionId, @RequestParam(required = false) String inviteId,
                           @RequestParam String sessionKey, @RequestParam String signature, @RequestParam String rawData,
                           @RequestParam String encryptedData, @RequestParam String iv, @RequestParam(required = false) String code,
                           HttpServletResponse response) {
        try {
            // 用户信息校验
            if (!maService.getWxMaService().getUserService().checkUserInfo(sessionKey, rawData, signature)) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }

            // 解密用户信息
            WxMaUserInfo userInfo = maService.getWxMaService().getUserService().getUserInfo(sessionKey, encryptedData, iv);
            if (userInfo == null) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("用户信息校验错误"));
            }
            UserThirdAuth userThirdAuth = this.userThirdAuthService.findByAuthIdAndLoginType(openId, LoginType.WX_MA);
            Member member;
            // 第三方登录信息不存在，首次注册登录
            if (userThirdAuth == null) {
                WxMaPhoneNumberInfo phoneNoInfo = getWxPhone(code);
                member = this.memberService.findByPhone(phoneNoInfo.getPurePhoneNumber());
                if (member == null) {
                    member = new Member();
                    member.setSource(MemberSource.WX_MA);
                    convert(member, userInfo, phoneNoInfo);
                    member = this.memberService.registerByMa(member, inviteId);
                }
                userThirdAuth = new UserThirdAuth();
                userThirdAuth.setMemberId(member.getId());
                userThirdAuth.setOauthId(openId);
                userThirdAuth.setUnionId(unionId);
                userThirdAuth.setLoginType(LoginType.WX_MA);
                this.userThirdAuthService.save(userThirdAuth);
            }else {
                member = this.memberService.getById(userThirdAuth.getMemberId());
                // 如果有第三方登录信息，但是没有用户信息或者手机号
                if (member == null) {
                    WxMaPhoneNumberInfo phoneNoInfo = getWxPhone(code);
                    member = new Member();
                    member.setSource(MemberSource.WX_MA);
                    convert(member, userInfo, phoneNoInfo);
                    member = this.memberService.registerByMa(member, inviteId);

                    userThirdAuth.setMemberId(member.getId());
                    userThirdAuth.setUnionId(unionId);
                    this.userThirdAuthService.updateById(userThirdAuth);
                }else {
                    // 没有手机号的用户需要重新获取手机号登录
                    if (!StringUtils.hasText(member.getPhone())) {
                        WxMaPhoneNumberInfo phoneNoInfo = getWxPhone(code);
                        member.setPhone(phoneNoInfo.getPurePhoneNumber());
                        member.setCountry(phoneNoInfo.getCountryCode());
                        member.setVersion(member.getVersion());
                        this.memberService.updateById(member);
                    }
                }
            }
            if (member.getStatus() == MemberStatus.DISABLED) {
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.ACCOUNT_LOCKED));
            }
            String token = TokenUtil.generateToken(member.getId(), sessionKey);
            response.addHeader(AppConst.TOKEN_HEAD_NAME, token);
            return ResBody.success(token);
        } finally {
            //清理ThreadLocal
            WxMaConfigHolder.remove();
        }
    }

    /**
     * 用户信息转换
     * @param member 用户实体
     * @param userInfo 微信用户授权信息
     * @param phoneNoInfo 微信手机号授权信息
     */
    private void convert(Member member, WxMaUserInfo userInfo, WxMaPhoneNumberInfo phoneNoInfo) {
        member.setPhone(phoneNoInfo.getPurePhoneNumber());
        member.setNickname(userInfo.getNickName());
        member.setAvatar(userInfo.getAvatarUrl());
        member.setGender(Gender.getByValue(Integer.valueOf(userInfo.getGender())));
        member.setCountry(userInfo.getCountry());
        member.setProvince(userInfo.getProvince());
        member.setCity(userInfo.getCity());
    }

    private WxMaPhoneNumberInfo getWxPhone(String code) {
        if (!StringUtils.hasText(code)) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.PHONE_NO_EXISTIS).message("请授权手机号"));
        }
        WxMaPhoneNumberInfo phoneNoInfo = null;
        try {
            phoneNoInfo = maService.getWxMaService().getUserService().getNewPhoneNoInfo(code);
        } catch (WxErrorException e) {
            log.error("微信获取手机号失败，未知错误", e);
        }
        if (null == phoneNoInfo) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("微信获取手机号失败，未知错误"));
        }
        return phoneNoInfo;
    }


    @Deprecated
    @TokenAuth
    @PostMapping("/phone")
    @ApiOperation(value = "更新用户绑定手机号")
    public ResBody<?> phone(@ApiIgnore @CurrentUser TokenModel token, String code) {
        // 解密
        WxMaPhoneNumberInfo phoneNoInfo;
        try {
            phoneNoInfo = maService.getWxMaService().getUserService().getNewPhoneNoInfo(code);
        } catch (WxErrorException e) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("微信获取手机号失败，未知错误"));
           return ResBody.failure();
        }
        if (null == phoneNoInfo) {
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("微信获取手机号失败，未知错误"));
        }
        Member member = this.memberService.getById(token.getId());
        member.setPhone(phoneNoInfo.getPurePhoneNumber());
        member.setCountry(phoneNoInfo.getCountryCode());
        member.setVersion(member.getVersion());
        this.memberService.updateById(member);

        WxMaConfigHolder.remove();//清理ThreadLocal
        return ResBody.success();
    }

    @Limit(period = 5, count = 5)
    @TokenAuth
    @PostMapping("/getQRCode")
    @ApiOperation(value = "获取二维码图片")
    public ResBody<?> getQRCode(@RequestParam String page, @CurrentUser @ApiIgnore TokenModel token){
        try {
            String scene = "inviteid=" + token.getId() + "&source=ma";
            // 兼容旧逻辑
            page = page.split("\\?")[0];
            page = page.startsWith("/") ? page.replaceFirst("/", "") : page;
            String envVersion = Objects.equals("test", env) ? "trial" : WxMaConstants.DEFAULT_ENV_VERSION;
            File qrCodeFile = this.maService.getWxMaService().getQrcodeService().createWxaCodeUnlimit(scene, page, true, envVersion, 430, true, null, false);
            String fileStr = encryptToBase64(qrCodeFile.getPath());
            if (!StringUtils.hasText(fileStr)){
                //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("获取二维码图片错误"));
            }
            String url = ossManager.putObject("qrcode/", bucketsName, fileStr);
            return ResBody.success(url);
        } catch (WxErrorException e) {
            log.error("获取二维码图片错误：{}", e.getError().toString());
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("获取邀请二维码图片错误"));
        } catch (IOException e) {
            log.error("邀请二维码图片上传到OSS错误：{}", e.getMessage());
            //psl throw new BizException(BizCodeFace.createBizCode(ErrorCode.FAIL).message("获取邀请二维码图片错误"));
        }
        return ResBody.failure();
    }

    private String encryptToBase64(String filePath) {
        if (filePath == null) {
            return null;
        }
        try {
            byte[] b = Files.readAllBytes(Paths.get(filePath));
            return Base64.getEncoder().encodeToString(b);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
